
#include "Self_Define_Functions.h"
#include "mpi.h"
#include <stdio.h>

using namespace std;

int main( int argc, char ** argv )
{

    Benchmarks *fp = NULL;

    int fun_num, total_run, run_index, MAX_GEN;
    //child process variables
    double **population;
    double **speed;
    double **archive;
    double *archive_results;
    double *population_results;
    int *current_top_best;
    int *non_top_best;
    double *mean_position;
    double *Lower_Bound;
    double *Upper_Bound;
    int archive_counter;
    double worst;
    int worst_index;
    double PHI;

    double PHI_MAX = 0.5;
    double PHI_MIN = 0;




    //root process variables
    double *fitness_results;
    double *time_results;
    double **pool;
    int Pool_Size;
    double *pool_results;
    int *last_elites;
    double start_time;
    double end_time;
    int pool_counter;
    int stop_counter;



    //common variables
    double *gbest;
    double gbest_result;
    int gbest_index;
    double *buffer;
    int selected_exemplar;


    int i,j,k;
    int process_id;
    int total_process;
	int generation_counter;
	int counter;




	fun_num = atoi( argv[1] );
	MAX_GEN = atoi( argv[2] );
	total_run = atoi( argv[3] );

	fp = generateFuncObj( fun_num );
	if( fun_num >= 1 && fun_num <= 3 )
        PHI_MAX = 0.1;
    else
        PHI_MAX = 0.5;


	MPI_Init( &argc, &argv );

	MPI_Comm_rank( MPI_COMM_WORLD, &process_id );
	MPI_Comm_size( MPI_COMM_WORLD, &total_process );


    if( process_id == 0 )
    {
        //the root process applies memory
        Pool_Size = max( Population_Size, total_process-1 );
        pool = new double*[Pool_Size];
        pool_results = new double[Pool_Size];

        for( i = 0; i < Pool_Size; ++i )
        {
            pool[i] = new double[Dim];
        }

        fitness_results = new double[total_run];
        time_results = new double[total_run];
    }
    else
    {

        //the child process applies the memory
        population = new double*[Population_Size];
        speed = new double*[Population_Size];
        archive = new double*[Elite_Number];

        for( i = 0; i < Population_Size; ++i )
        {
            population[i] = new double[Dim];
            speed[i] = new double[Dim];
        }

        for( i = 0; i < Elite_Number; ++i )
        {
            archive[i] = new double[Dim];
        }

        population_results = new double[Population_Size];
        archive_results = new double[Elite_Number];

        last_elites = new int[Elite_Number];

        current_top_best = new int[Elite_Number];
        non_top_best = new int[Population_Size-Elite_Number];

        Lower_Bound = new double[Dim];
        Upper_Bound = new double[Dim];

        mean_position = new double[Dim];

	}

    gbest = new double[Dim+1]; //the last element is to store the fitness of gbest

	buffer = new double[(Dim+1)]; //used to transfer data

    MPI_Status status;

    double finish = -1;

    boost::mt19937 generator(time(0)*rand());

	if( process_id == 0 )//root process
	{


        printf( "Function %d Begins!\n", fun_num );


        for ( run_index = 0; run_index < total_run; ++run_index )
        {
            pool_counter = 0;
            stop_counter = 0;
            gbest_result = 1e100;


            MPI_Barrier( MPI_COMM_WORLD);

            start_time = MPI_Wtime();

            /*
            Tag:
                0: receive gbest from the children process
                1: the application of child process to introduce candidates
                2: receive the final gbest from the child process
            */

            while (1)
            {
                if( stop_counter == total_process-1 ) //this run is finished
                    break;


                MPI_Recv( buffer, Dim+1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status ); //receive data from all child processes

                if( 2 == status.MPI_TAG )//store the gbest
                {
                    if( buffer[Dim] < gbest_result )
                    {
                        gbest_result = buffer[Dim];
                        memcpy( gbest, buffer, sizeof(double)*Dim );
                    }
                    stop_counter++;
                }
                else if( 1 == status.MPI_TAG ) // Tag == 1 indicates the child process sending this message needs to import candidates
                {

                    boost::uniform_int<> uniform_int_generator_root( 0, pool_counter-1 );
                    boost::variate_generator< boost::mt19937&, boost::uniform_int<> > uniform_int_number_root( generator, uniform_int_generator_root );

                    selected_exemplar = uniform_int_number_root();

                    memcpy( buffer, pool[selected_exemplar], sizeof(double)*Dim );
                    buffer[Dim] = pool_results[selected_exemplar];

                    MPI_Send( buffer, (Dim+1), MPI_DOUBLE, status.MPI_SOURCE, 1, MPI_COMM_WORLD );

                }
                else if( 0 == status.MPI_TAG ) // Tag == 0 indicates the root process receives gbest from the child process
                {
                    if( pool_counter >= Pool_Size )
                    {
                        Update_Pool_Random( buffer, buffer[Dim], pool, pool_results, Pool_Size, Dim );
                    }
                    else
                    {
                        memcpy( pool[pool_counter], buffer, sizeof(double)*Dim );
                        pool_results[pool_counter] = buffer[Dim];
                        pool_counter ++;
                    }
                }


            }

            end_time = MPI_Wtime();

            fitness_results[run_index] = gbest_result;
            time_results[run_index] = end_time - start_time;

        }

        //store the results
        char fun[10];
        snprintf(fun,10,"%d",fun_num);
        string filename_fitness = "./Results_Fitness/Fitness_result_for_function_"+ string(fun)+".txt";
        ofstream out_fitness(filename_fitness.c_str());
        if(!out_fitness)
        {
            cerr<<"Can not open the file " <<filename_fitness<<endl;
            exit(1);
        }

        string filename_Time = "./Results_Time/Time_result_for_function_"+ string(fun)+".txt";
        ofstream out_Time(filename_Time.c_str());
        if(!out_Time)
        {
            cerr<<"Can not open the file " <<filename_Time<<endl;
            exit(1);
        }

        for( run_index = 0; run_index < total_run; ++run_index )
        {

            out_fitness<<fitness_results[run_index]<<endl;

            out_Time<<time_results[run_index]<<endl;
        }


        out_fitness.close();
        out_Time.close();

        printf( "Function %d Finishes!\n", fun_num );

	}
	else//child process
	{
        generation_counter = 0;

        double candidate_application = -1;

        for( i = 0; i < Dim; ++i )
        {
            Lower_Bound[i] = fp->getMinX();
            Upper_Bound[i] = fp->getMaxX();
        }


        for( run_index = 0; run_index < total_run; ++run_index )
        {
            MPI_Barrier( MPI_COMM_WORLD);
            generation_counter = 0;



            boost::uniform_real<> uniform_real_generator( Lower_Bound[0], Upper_Bound[0] );
            boost::variate_generator< boost::mt19937&, boost::uniform_real<> > uniform_real_number( generator, uniform_real_generator );

            //initialize the population
            for( i = 0; i < Population_Size; ++i )
            {
                for( j = 0; j < Dim; ++j )
                {
                    population[i][j] = uniform_real_number();
                    speed[i][j] = 0;
                }

                population_results[i] = fp->compute( population[i] );

            }

            gbest_result = 1e100;
            archive_counter = 0;

            while ( generation_counter < MAX_GEN )
            {
                Get_Elites( current_top_best, non_top_best, Elite_Number, Population_Size-Elite_Number, population_results, Population_Size );

                PHI = PHI_MAX - (PHI_MAX-PHI_MIN)*(double)(generation_counter/MAX_GEN);

                if( population_results[current_top_best[0]] < gbest_result )
                {
                    gbest_result = population_results[current_top_best[0]];

                    memcpy( gbest, population[current_top_best[0]], sizeof(double)*Dim );
                    gbest[Dim] = population_results[current_top_best[0]];
                    //send a random elite to the root process
                    MPI_Send( gbest, Dim+1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD );
                }

                Elite_Mean( mean_position, current_top_best, population, Elite_Number, Dim );


                if( generation_counter !=0 && Intersection( last_elites, current_top_best, Elite_Number ) > 0 )
                {
                    //send the candidate application signal to the root process
                    MPI_Send( &candidate_application, 1, MPI_DOUBLE, 0, 1, MPI_COMM_WORLD );

                    //receive the candidate data
                    MPI_Recv( buffer, (Dim+1), MPI_DOUBLE, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status );

                    if( archive_counter >= Elite_Number )
                    {
                        worst = archive_results[0];
                        worst_index = 0;

                        for( i = 1; i< Elite_Number; ++i )
                        {
                            if( worst < archive_results[i] )
                            {
                                worst = archive_results[i];
                                worst_index = i;
                            }
                        }

                        if( buffer[Dim] < archive_results[worst_index] )
                        {
                            archive_results[worst_index] = buffer[Dim];
                            memcpy( archive[worst_index], buffer, sizeof(double)*Dim );
                        }
                    }
                    else
                    {
                        memcpy( archive[archive_counter], buffer, sizeof(double)*Dim );
                        archive_results[archive_counter] = buffer[Dim];
                        archive_counter++;
                    }
                }


               for ( i = 0; i < Population_Size-Elite_Number; ++i )
                {
                    boost::uniform_int<> uniform_int_generator( 0, Elite_Number+archive_counter-1 );
                    boost::variate_generator< boost::mt19937&, boost::uniform_int<> > uniform_int_number( generator, uniform_int_generator );


                    do
                    {
                        selected_exemplar = uniform_int_number();
                        if( selected_exemplar >= Elite_Number)
                        {
                            if( archive_results[selected_exemplar-Elite_Number] <= population_results[non_top_best[i]] )
                                break;
                        }
                        else
                        {
                            break;
                        }

                    }while( 1 );



                    if( selected_exemplar >= Elite_Number )
                    {
                        Individual_Update( population[non_top_best[i]], speed[non_top_best[i]], archive[selected_exemplar-Elite_Number], mean_position, Lower_Bound, Upper_Bound, Dim, PHI );
                    }
                    else
                    {
                        Individual_Update( population[non_top_best[i]], speed[non_top_best[i]],  population[current_top_best[selected_exemplar]], mean_position, Lower_Bound, Upper_Bound, Dim, PHI );

                    }

                    population_results[non_top_best[i]] = fp->compute( population[non_top_best[i]] );
                }

                memcpy( last_elites, current_top_best, sizeof(int)*Elite_Number );

                generation_counter++;

            }


            Get_Elites( current_top_best, non_top_best, Elite_Number, Population_Size-Elite_Number, population_results, Population_Size );

            memcpy( gbest, population[current_top_best[0]], sizeof(double)*Dim );
            memcpy( gbest+Dim, &population_results[current_top_best[0]], sizeof(double)*1 );
            //send gbest to the root process
            MPI_Send( gbest, Dim+1, MPI_DOUBLE, 0, 2, MPI_COMM_WORLD );

        }


	}

	//release resource
    if( process_id == 0 )
    {
        //the root process applies memory

        for( i = 0; i < Pool_Size; ++i )
        {
            delete []pool[i];
        }
        delete []pool;

        delete []pool_results;

        delete []fitness_results;
        delete []time_results;

    }
    else
    {

        //the child process applies the memory


        for( i = 0; i < Population_Size; ++i )
        {
            delete []population[i];
            delete []speed[i];
        }
        delete []population;
        delete []speed;

        for( i = 0; i < Elite_Number; ++i )
            delete []archive[i];

        delete []archive;

        delete []population_results;

        delete []archive_results;

        delete []current_top_best;
        delete []non_top_best;

        delete []Lower_Bound;
        delete []Upper_Bound;
        delete []mean_position;
        delete []last_elites;

	}

    delete []gbest; //the last element is to store the fitness of gbest

	delete []buffer; //used to transfer data


     MPI_Finalize();


    return 0;
}
